﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace RconLibrary.Protocol
{
    /// <summary>
    /// Protocol class.
    /// </summary>
    public class Processor
    {
        #region Variables

        /// <summary>
        /// Current write position.
        /// </summary>
        private int dataLength;

        /// <summary>
        /// Buffer storage.
        /// </summary>
        private byte[] buffer = null;

        /// <summary>
        /// Maximum buffer size (in bytes).
        /// </summary>
        private int maxBufferSize;

        #endregion

        #region Events

        /// <summary>
        /// Request handler.
        /// </summary>
        /// <param name="packet">Received packet.</param>
        public delegate void PacketHandler(Packet packet);

        /// <summary>
        /// Request event.
        /// </summary>
        public event PacketHandler Request;

        /// <summary>
        /// Response event.
        /// </summary>
        public event PacketHandler Response;

        #endregion

        /// <summary>
        /// Creates new instance of the protocol handler.
        /// </summary>
        /// <param name="size">Initial size of the buffer.</param>
        /// <param name="maxSize">Maximum buffer size allowed.</param>
        public Processor(int size = 4096, int maxSize = 32768)
        {
            this.maxBufferSize = maxSize;
            this.buffer = new byte[size];
        }

        /// <summary>
        /// Appends incoming data to the buffer tail.
        /// </summary>
        /// <param name="incoming">Incoming socket buffer.</param>
        public void Process(byte[] incoming)
        {
            // sanity check.
            if (incoming.Length == 0)
            {
                return;
            }

            // if the buffer cannot hold the incoming packet, we try to grow it up.
            int available = this.buffer.Length - this.dataLength;
            if (available < incoming.Length)
            {
                // check if we can increase buffer size... 
                int tempSize = (int)(this.buffer.Length * 1.5);
                if (tempSize > this.maxBufferSize)
                {
                    throw new Exception("Maxium buffer size exceeded.");
                }

                // copies the current data to the new buffer
                byte[] temp = new byte[tempSize];
                Buffer.BlockCopy(this.buffer, 0, temp, 0, this.dataLength);
                this.buffer = temp;

                // try to process the incoming data again
                this.Process(incoming);
            }
            else
            {
                // we have space, copy the incoming data to the buffer's tail.
                Buffer.BlockCopy(incoming, 0, this.buffer, this.dataLength, incoming.Length);
                this.dataLength += incoming.Length;
            }

            // try reading some packets
            int bytes = 0;
            Packet packet = null;
            while ((bytes = this.ReadPacket(out packet)) != 0)
            {
                int remainingBytes = this.dataLength - bytes;
                if (remainingBytes > 0)
                {
                    byte[] remaining = new byte[remainingBytes];
                    Buffer.BlockCopy(this.buffer, bytes, remaining, 0, remaining.Length);
                    Buffer.BlockCopy(remaining, 0, this.buffer, 0, remaining.Length);
                    this.dataLength -= bytes;
                }
                else if (remainingBytes == 0)
                {
                    this.dataLength = 0;
                }
                else
                {
                    throw new Exception("Packet length is greater than the actual bytes in the buffer.");
                }

                // we have a valid packet... 
                // redirect them to the proper event dispatcher.
                if (/*packet.Origin == PacketOrigin.Server && */ packet.Type == PacketType.Request)
                {
                    if (this.Request != null)
                    {
                        this.Request(packet);
                    }
                }
                else if (/*packet.Origin == PacketOrigin.Client && */ packet.Type == PacketType.Response)
                {
                    if (this.Response != null)
                    {
                        this.Response(packet);
                    }
                }
                else
                {
                    throw new Exception("Invalid packet received.");
                }
            }

        }

        /// <summary>
        /// Reads a packet from the buffer.
        /// </summary>
        /// <returns>Packet</returns>
        private int ReadPacket(out Packet packet)
        {
            packet = null;

            if (this.dataLength < 8)
            {
                return 0; // invalid packet (incomplete header)
            }

            using (var reader = new DataReader(ref this.buffer))
            {
                uint head = reader.ReadUInt();
                uint size = reader.ReadUInt();

                if (size > this.dataLength)
                {
                    return 0;
                }

                int wordCount = reader.ReadInt();
                List<string> words = new List<string>();

                for (int i = 0; i < wordCount; i++)
                {
                    int bytes = reader.ReadInt();
                    string word = Encoding.ASCII.GetString(reader.ReadBytes(bytes));
                    reader.ReadByte(); // null terminator
                    words.Add(word);
                }

                PacketType type = (head & 0x40000000) == 0 ? PacketType.Request : PacketType.Response;
                PacketOrigin origin = (head & 0x80000000) == 0 ? PacketOrigin.Server : PacketOrigin.Client;
                packet = new Packet((int)(head & 0x3FFFFFFF), origin, type, words);

                return reader.Position;

            }

        }

    }

}

